#pragma once
// -------------------------------------------------
// ZED Kit
// -------------------------------------------------
//   File Name: core_foundation.hpp
//      Author: Ziming Li
//     Created: 2023-11-12
// -------------------------------------------------
// Copyright (C) 2023 MingYang Software Technology.
// -------------------------------------------------

#ifndef ZED_APPLE_CORE_FOUNDATION_HPP
#define ZED_APPLE_CORE_FOUNDATION_HPP

#include <CoreFoundation/CoreFoundation.h>

namespace zed {

template <typename T>
struct cf_release_trait {
    static void release(T cf) { ::CFRelease(cf); }
};

template <typename T>
class cf_retained
{
public:
    cf_retained(T cf = nullptr) : m_cf(cf) { }
    cf_retained(const cf_retained& o);
    ~cf_retained(void);

    operator bool() const { return nullptr != m_cf; }
    operator T() const { return m_cf; }
    cf_retained& operator=(T cf);
    cf_retained& operator=(const cf_retained& o);

    T get(void) const { return m_cf; }
    void release(void);
private:
    T m_cf;
};

class cf_number : public cf_retained<CFNumberRef>
{
public:
    using cf_retained::cf_retained;

    bool get_value(int& dst) const {
        return ::CFNumberGetValue(get(), kCFNumberIntType, &dst);
    }
};

class cf_string : public cf_retained<CFStringRef>
{
public:
    using cf_retained::cf_retained;

    std::string get_utf8(void) const;
};

class cf_dictionary : public cf_retained<CFDictionaryRef>
{
public:
    using cf_retained::cf_retained;

    template <typename T>
    bool get_value(const void* key, cf_retained<T>& dst) const;
    bool get_int(const void* key, int& dst) const;
    bool get_string(const void* key, std::string& dst) const;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations

namespace detail {

template <typename T>
struct cf_traits {
    static CFTypeID type_id(void);
};

template <>
struct cf_traits<CFNumberRef> {
    static CFTypeID type_id(void) { return ::CFNumberGetTypeID(); }
};

template <>
struct cf_traits<CFStringRef> {
    static CFTypeID type_id(void) { return ::CFStringGetTypeID(); }
};

} // namespace detail

template <typename T>
cf_retained<T>::cf_retained(const cf_retained& o) : m_cf(o.m_cf)
{
    if (nullptr != m_cf)
        ::CFRetain(m_cf);
}

template <typename T>
cf_retained<T>::~cf_retained(void)
{
    if (nullptr != m_cf)
        cf_release_trait<T>::release(m_cf);
}

template <typename T>
cf_retained<T>& cf_retained<T>::operator=(T cf)
{
    if (m_cf != cf)
    {
        if (nullptr != m_cf)
            cf_release_trait<T>::release(m_cf);
        m_cf = cf;
    }
    return *this;
}

template <typename T>
cf_retained<T>& cf_retained<T>::operator=(const cf_retained& o)
{
    if (m_cf != o.m_cf)
    {
        if (nullptr != m_cf)
            cf_release_trait<T>::release(m_cf);
        m_cf = o.m_cf;
        if (nullptr != m_cf)
            CFRetain(m_cf);
    }
    return *this;
}

template <typename T>
void cf_retained<T>::release(void)
{
    if (nullptr != m_cf)
    {
        cf_release_trait<T>::release(m_cf);
        m_cf = nullptr;
    }
}

inline std::string cf_string::get_utf8(void) const
{
    std::string ret;

    CFIndex length = ::CFStringGetLength(get());
    CFIndex max_length = ::CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);

    ret.resize(max_length);
    ::CFStringGetBytes(get(), CFRangeMake(0, length), kCFStringEncodingUTF8, '?', true, reinterpret_cast<UInt8*>(const_cast<char*>(ret.data())), max_length, &length);
    ret.resize(length);
    return ret;
}

template <typename T>
bool cf_dictionary::get_value(const void* key, cf_retained<T>& dst) const
{
    cf_retained<CFTypeRef> v = ::CFDictionaryGetValue(get(), key);
    if (!v || ::CFGetTypeID(v) != detail::cf_traits<T>::type_id())
        return false;

    dst = static_cast<T>(v.get());
    return true;
}

inline bool cf_dictionary::get_int(const void* key, int& dst) const
{
    cf_number v;
    if (!get_value(key, v))
        return false;
    return v.get_value(dst);
}

inline bool cf_dictionary::get_string(const void* key, std::string& dst) const
{
    cf_string v;
    if (!get_value(key, v))
        return false;
    dst = v.get_utf8();
    return true;
}

} // namespace zed

#endif // ZED_APPLE_CORE_FOUNDATION_HPP
